﻿using System;
using System.Collections.Generic;
using ParserGenerator.CharacterMapping;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.ScannerConstruction
{
    [Serializable]
    public sealed class ScannerAutomatonState
    {
        public int Number { get; internal set; }
        public ScannerAutomatonStateTable Table { get; }
        public ScannerAutomatonTransitions Transitions { get; }
        public ScannerAutomatonReductions Reductions { get; }
        public ScannerAutomatonStatePredecessors Predecessors => Table.Predecessors[this];
        internal ScannerAutomatonItemSet Core { get; set; }
        internal ScannerAutomatonStateGroup Group { get; set; }

        internal ScannerAutomatonState(ScannerAutomatonStateTable table, ScannerAutomatonItemSet core)
        {
            if (table == null)
                throw new ArgumentNullException(nameof(table));
            if (core == null)
                throw new ArgumentNullException(nameof(core));
            Number = table.Count;
            Table = table;
            Transitions = new ScannerAutomatonTransitions(this);
            Reductions = new ScannerAutomatonReductions(this);
            Core = core;
        }

        internal Dictionary<CharClass, ScannerAutomatonItemSet> Partition()
        {
            var dict = new Dictionary<CharClass, ScannerAutomatonItemSet>();
            foreach (ScannerAutomatonItem item in Core)
            {
                if (item.IsReduction)
                    AddReduction(item.AsReduction.Rule);
                else
                {
                    ScannerAutomatonItemSet group;
                    if (!dict.TryGetValue(item.AsShift.Symbol, out group))
                    {
                        group = new ScannerAutomatonItemSet();
                        dict.Add(item.AsShift.Symbol, group);
                    }
                    group.UnionWith(item.FollowSet);
                }
            }
            if (Table.ScannerAutomaton.UseLiteralFirstResolutions)
                Reductions.ApplyLiteralFirstResolutions();
            return dict;
        }

        internal void AddTransition(CharClass symbol, ScannerAutomatonState target)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            if (target == null)
                throw new ArgumentNullException(nameof(target));
            Transitions.Add(symbol, target);
        }

        internal void AddReduction(LexicalRuleDeclarator rule)
        {
            if (rule == null)
                throw new ArgumentNullException(nameof(rule));
            Reductions.Add(rule);
        }

        internal void UpdateTransition(CharClass symbol, ScannerAutomatonState target)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            if (target == null)
                throw new ArgumentNullException(nameof(target));
            Transitions.Update(symbol, target);
        }

        public ScannerAutomatonTransition GetTransition(CharClass symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            return Transitions.GetTransition(symbol);
        }

        public ScannerAutomatonTransition TryGetTransition(CharClass symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            return Transitions.TryGetTransition(symbol);
        }

        internal bool CanMergeWith(ScannerAutomatonState other)
        {
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            if (other == this)
                return true;
            if (other.Transitions.Count != Transitions.Count)
                return false;
            if (!other.Reductions.SetEquals(Reductions))
                return false;
            foreach (var t in Transitions)
            {
                ScannerAutomatonTransition tran = other.TryGetTransition(t.Key);
                if (tran == null || tran.Target.Group != t.Value.Target.Group)
                    return false;
            }
            return true;
        }

        public sealed override string ToString()
        {
            return Number.ToString();
        }
    }
}
